/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package tools;

import com.openbrew.openbrewrf.Administration;
import com.openbrew.openbrewrf.BoilHopNotes;
import com.openbrew.openbrewrf.BoilHops;
import com.openbrew.openbrewrf.BrewNotes;
import com.openbrew.openbrewrf.Brews;
import com.openbrew.openbrewrf.DryHops;
import com.openbrew.openbrewrf.FinishedGoodsInventory;
import com.openbrew.openbrewrf.Hops;
import com.openbrew.openbrewrf.InvPSOLines;
import com.openbrew.openbrewrf.InvSalesLines;
import com.openbrew.openbrewrf.InventoryUsage;
import com.openbrew.openbrewrf.Malt;
import com.openbrew.openbrewrf.MaltBill;
import com.openbrew.openbrewrf.MaltNotes;
import com.openbrew.openbrewrf.POLines;
import com.openbrew.openbrewrf.PSOLines;
import com.openbrew.openbrewrf.PendingSO;
import com.openbrew.openbrewrf.PurchaseOrder;
import com.openbrew.openbrewrf.RecipeAdditives;
import com.openbrew.openbrewrf.Sales;
import com.openbrew.openbrewrf.SalesOrder;
import com.openbrew.openbrewrf.ScanTblKeg;
import com.openbrew.openbrewrf.Supplies;
import com.openbrew.openbrewrf.YeastGens;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import service.enumerations.MovementState;
import service.enumerations.Units;
import service.enumerations.WarehouseIngredient;

/**
 *
 * @author dchapman
 */
public class InventoryModifiers {
    /***************************************************************************
     * Sales Order Modification routines.
     **************************************************************************/
    /***************************************************************************
     * doSalesOrderLineDeletionCheck
     * @param en
     * @param newSO 
     **************************************************************************/
    public void doSalesOrderLineDeletionCheck(final EntityManager en, SalesOrder newSO){
        SalesOrder oldSO = getSalesOrderFromID(en, newSO.getSOId());
        if(oldSO.getSalesCollection()!=null){
            for(Sales oldSale : oldSO.getSalesCollection()){
                boolean found = false;
                final int saleID = oldSale.getSId();
                if(newSO.getSalesCollection()!=null && !newSO.getSalesCollection().isEmpty()){
                    for(int i=0; i<newSO.getSalesCollection().size(); i++){
                        if(newSO.getSalesCollection().get(i).getSId() == saleID){
                            found = true;
                        }
                    }
                }
                if(!found){
                    doFGIReturnWithoutAllocation(en, oldSale.getSalesFGIID().getFgiid()
                        , oldSale.getSalesFGIQty());
                    if(oldSale.getKegID() != 0){
                        //Need to reset keg info.
                        ScanTblKeg keg = getKegFromID(en, oldSale.getKegID());
                        keg.setKegCust(null);
                        keg.makeKegCustNull();
                        keg.setDateSold(null);
                        en.merge(keg);
                    }
                }
            }
        }
        if(oldSO.getInvSalesLinesCollection()!=null){
            for(InvSalesLines oldSale : oldSO.getInvSalesLinesCollection()){
                boolean found = false;
                final int saleID = oldSale.getInvSaleID();
                if(newSO.getInvSalesLinesCollection()!=null && !newSO.getInvSalesLinesCollection().isEmpty()){
                    for(int i=0; i<newSO.getInvSalesLinesCollection().size(); i++){
                        if(newSO.getInvSalesLinesCollection().get(i).getInvSaleID() == saleID){
                            found = true;
                        }
                    }
                }
                if(!found){
                    doSupplyReturnWithoutAllocation(en, oldSale.getInvSaleSupplyID().getSupID()
                        , oldSale.getInvSaleQty());
                }
            }
        }
        en.getEntityManagerFactory().getCache().evictAll();
    }
    /***************************************************************************
     * Check Sale lines for changes.
     * @param en - EntityManager
     * @param sale - Sale to be checked.
     **************************************************************************/
    public void doSaleLineCheck(final EntityManager en, Sales sale){
        Sales oldLine = getSaleFromID(en, sale.getSId());
        if(!sale.getSalesFGIID().getFGIPackageID().isServingVessel()){
            //Run this for non serving vessel.
            final int oldQty = oldLine.getSalesFGIQty();
            final int newQty = sale.getSalesFGIQty();
            if(oldQty > newQty){
                //Do addition from inventory
                doFGIReturnWithoutAllocation(en, oldLine.getSalesFGIID().getFgiid()
                    , oldQty - newQty);
            } else if (oldQty < newQty){
                //Do removal from inventory
                doFGIRemovalWithoutAllocation(en, oldLine.getSalesFGIID().getFgiid()
                    , newQty - oldQty);
            }
            if(oldLine.getKegID() != sale.getKegID()){
                if(!doSaleKegIDChange(en, oldLine.getKegID(), sale.getKegID())){
                    sale.setKegID(oldLine.getKegID());
                }
            }
        } else {
            //Run this for serving vessel.
            final float oldVolume = oldLine.getServingTankVolume();
            final float newVolume = sale.getServingTankVolume();
            if(oldVolume > newVolume){
                //Do addition from inventory
                doFGIServingVesselReturnWithoutAllocation(en, oldLine.getSalesFGIID().getFgiid()
                        , oldVolume - newVolume);
            } else if(oldVolume < newVolume){
                //Do removal from inventory
                doFGIServingTankRemovalWithoutAllocation(en, oldLine.getSalesFGIID().getFgiid()
                        , newVolume - oldVolume);
            }
        }
    }
    
    public void doInvSaleLineCheck(final EntityManager en, final InvSalesLines sale){
        InvSalesLines oldLine = getInvSaleFromID(en, sale.getInvSaleID());
        final int oldQty = oldLine.getInvSaleQty();
        final int newQty = sale.getInvSaleQty();
        doSupplyAdjustmentWithoutAllocation(en, oldLine.getInvSaleSupplyID().getSupID()
                , oldQty, newQty);
    }
    
    private boolean doSaleKegIDChange(final EntityManager en, final int oldKegID, final int newKegID){
        ScanTblKeg oldKeg = getKegFromID(en, oldKegID);
        ScanTblKeg newKeg = getKegFromID(en, newKegID);
        
        if((newKeg.getKegBeer().getbId() == oldKeg.getKegBeer().getbId())
                &&(newKeg.getKegPackageID().getPackageTypeID() == oldKeg.getKegPackageID().getPackageTypeID())){
            newKeg.setKegCust(oldKeg.getKegCust());
            oldKeg.setKegCust(null);
            Date tmpDate = oldKeg.getDateSold();
            oldKeg.setDateSold(newKeg.getDateSold());
            newKeg.setDateSold(tmpDate);
            en.merge(oldKeg);
            en.merge(newKeg);
            return true;
        } else {
            return false;
        }
    }
    
    private Sales getSaleFromID(final EntityManager en, final int saleID){
        TypedQuery<Sales> query = en.createQuery(
            "SELECT s FROM Sales s WHERE s.sId = ?1", Sales.class);
        query.setParameter(1, saleID);
        return query.getSingleResult();
    }
    
    private InvSalesLines getInvSaleFromID(final EntityManager en, final int saleID){
        TypedQuery<InvSalesLines> query = en.createQuery(
            "SELECT s FROM InvSalesLines s WHERE s.invSaleID = ?1", InvSalesLines.class);
        query.setParameter(1, saleID);
        return query.getSingleResult();
    }
    
    private ScanTblKeg getKegFromID(final EntityManager en, final int kegID){
        TypedQuery<ScanTblKeg> query = en.createQuery(
            "SELECT s FROM ScanTblKeg s WHERE s.kegID = ?1", ScanTblKeg.class);
        query.setParameter(1, kegID);
        return query.getSingleResult();
    }
    
    private SalesOrder getSalesOrderFromID(final EntityManager en, final int soID){
        TypedQuery<SalesOrder> query = en.createQuery(
            "SELECT s FROM SalesOrder s WHERE s.sOId = ?1", SalesOrder.class);
        query.setParameter(1, soID);
        return query.getSingleResult();
    }
    
    /***************************************************************************
     * Supplies Modifiers
     **************************************************************************/
    /***************************************************************************
     * Do a supply removal without adjusting allocation
     * @param en - Entity Manager
     * @param supID - Supply ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doSupplyRemovalWithoutAllocation(final EntityManager en
            , final int supID, final float qty){
        TypedQuery<Supplies> query = en.createQuery(
                "SELECT s FROM Supplies s WHERE s.supID = ?1", Supplies.class);
        query.setParameter(1, supID);
        Supplies supply = query.getSingleResult();
        float qtyLeft = supply.getSupOnHand() - qty;
        if(qtyLeft < 0){
            qtyLeft = 0;
        }
        supply.setSupOnHand(qtyLeft);
        en.merge(supply);
    }
    /***************************************************************************
     * Do a supply removal and adjust allocation
     * @param en - Entity Manager
     * @param supID - Supply ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doSupplyRemovalWithAllocation(final EntityManager en
            , final int supID, final float qty){
        TypedQuery<Supplies> query = en.createQuery(
                "SELECT s FROM Supplies s WHERE s.supID = ?1", Supplies.class);
        query.setParameter(1, supID);
        Supplies supply = query.getSingleResult();
        float qtyLeft = supply.getSupOnHand() - qty;
        float allocated = supply.getSupSpoken() - qty;
        if(qtyLeft < 0){
            qtyLeft = 0;
        }
        if(allocated < 0){
            allocated = 0;
        }
        supply.setSupOnHand(qtyLeft);
        supply.setSupSpoken(allocated);
        en.merge(supply);
    }
    
    /***************************************************************************
     * Do a supply return without adjusting allocation
     * @param en - Entity Manager
     * @param supID - Supply ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doSupplyReturnWithoutAllocation(final EntityManager en
            , final int supID, final float qty){
        TypedQuery<Supplies> query = en.createQuery(
                "SELECT s FROM Supplies s WHERE s.supID = ?1", Supplies.class);
        query.setParameter(1, supID);
        Supplies supply = query.getSingleResult();
        supply.setSupOnHand(supply.getSupOnHand() + qty);
        en.merge(supply);
    }
    
    /***************************************************************************
     * Do a supply adjustment without adjusting allocation
     * @param en - Entity Manager
     * @param supID - Supply ID to be modified.
     * @param oldQty
     * @param newQty
     **************************************************************************/
    public void doSupplyAdjustmentWithoutAllocation(final EntityManager en
            , final int supID, final float oldQty, final float newQty){
        if(oldQty > newQty){
            //Old qty was greater, so we need to return an item(s).
            doSupplyReturnWithoutAllocation(en, supID, (oldQty - newQty));
        } else if(oldQty < newQty){
            //New qty is greater, so we need to remove an item(s).
            doSupplyRemovalWithoutAllocation(en, supID, (newQty - oldQty));
        }
    }
    
    /***************************************************************************
     * Update all SAFs based on system pending usage only.
     * @param en - Entity Manager
     **************************************************************************/
    public void runSupplyAllocationFix(final EntityManager en){
        final Date stopDate = getPODate(en);
        List<Supplies> supplies = en.createNamedQuery("Supplies.findAll").getResultList();
        List<Brews> brewItems = en.createNamedQuery("Brews.findInProcess").getResultList();
        List<PendingSO> psos = en.createNamedQuery("PendingSO.findOpenPSOs").getResultList();
        List<PurchaseOrder> pos = en.createNamedQuery("PurchaseOrder.findOnOrderPOs").getResultList();
        for(final Supplies supply : supplies){
            float supplyAll = 0;
            for(final Brews brew : brewItems){
                if(brew.getGanttStart().before(stopDate) || brew.getGanttStart().equals(stopDate)){
                    //First do all supplies used in brews.
                    if(brew.getBState() < MovementState.FERMENTING.getState() && supply.getSupWH() == 130){
                        //First do any spice or flavoring used in beer for brews that aren't in production yet
                        supplyAll += getSAFAmt(brew, supply, en);
                    } else if(brew.getBState() < MovementState.KEGGED.getState()){
                        //Now do for any supply used in a brew (I.e. scheduled FGIs)
                        supplyAll += getSupplyAmt(brew, supply, en);
                    }
                }
            }
            for(PendingSO pso : psos){
                if(pso.getInvPSOLinesCollection()!=null){
                    for(InvPSOLines psol : pso.getInvPSOLinesCollection()){
                        if(psol.getInvPSOSupplyID().getSupID() == supply.getSupID()){
                            supplyAll += (float)psol.getInvPSOQtyLeft();
                        }
                    }
                }
            }
            if(supplyAll != supply.getSupSpoken()){
                supply.setSupSpoken(supplyAll);
                en.merge(supply);
            }
        }
        for(final Supplies supply : supplies){
            //Now reconcile what is on order and what isn't.
            int supplyOnOrder = 0;
            for(final PurchaseOrder po : pos){
                if(po.getPOLinesCollection()!=null){
                    for(final POLines pol : po.getPOLinesCollection()){
                        if((pol.getPolpn().getWhid().getWearhouseID() != WarehouseIngredient.HOPS.getDbId())
                                && (pol.getPolpn().getWhid().getWearhouseID() != WarehouseIngredient.MALT_AND_ADJUNCTS.getDbId())
                                && (pol.getPolpn().getWhid().getWearhouseID() != WarehouseIngredient.YEAST.getDbId())){
                            if(pol.getPolpn().getTableID() == supply.getSupID()){
                                if(!pol.getPOLRcvd()){
                                    int qty = (pol.getPOLQty() - pol.getpOLQtyRcvd());
                                    if(qty<0){
                                        qty=0;
                                    }
                                    supplyOnOrder += qty;
                                }
                            }
                        }
                    }
                }
            }
            supply.setSupOnOrder((float)supplyOnOrder);
        }
    }
        
    private double getSupplyAmt(final Brews brew, final Supplies supply, final EntityManager en){
        double retVal = 0;
        String[] fields = brew.getGanttFGI().split(",");
        int index = 0;
        if(!brew.getGanttFGI().isEmpty()){
            while(index < fields.length){
                final int id = Integer.parseInt(fields[index].trim());
                final FinishedGoodsInventory fgi = getFGIFromID(id, en);
                if(!fgi.getFGIPackageID().isServingVessel()){
                    final int qty = Integer.parseInt(fields[index + 1]);
                    if(qty > 0){
                        //remove old values from inventory allocation.
                        if(fgi.getInvUsageCollection()!=null){
                            for(final InventoryUsage iu : fgi.getInvUsageCollection()){
                                if(iu.getSupplyID().getSupID() == supply.getSupID()){
                                    retVal += modAndGetAmount(supply, iu.getUnits(), iu.getAmount(), qty);
                                }
                            }
                        }
                        if(fgi.getFGIPackageID().getInvUsageCollection()!=null){
                            for(final InventoryUsage iu : fgi.getFGIPackageID().getInvUsageCollection()){
                                if(iu.getSupplyID().getSupID() == supply.getSupID()){
                                    retVal += modAndGetAmount(supply, iu.getUnits(), iu.getAmount(), qty);
                                }
                            }
                        }
                    }
                }
                index = index + 2;
            }
        }
        return retVal;
    }
    
    private FinishedGoodsInventory getFGIFromID(final int id, final EntityManager en){
        TypedQuery<FinishedGoodsInventory> query = en.createQuery(
                "SELECT f FROM FinishedGoodsInventory f WHERE f.fgiid = ?1"
                , FinishedGoodsInventory.class);
        query.setParameter(1, id);
        final FinishedGoodsInventory fgi = query.getSingleResult();
        return fgi;
    }
    
    private double getSAFAmt(final Brews brew, final Supplies supply, final EntityManager en){
        int count = getBrewMaltCount(brew);
        float retVal = 0;
        List<RecipeAdditives> safs = brew.getRid().getAdditiveCollection();
        for(RecipeAdditives saf : safs){
            if(saf.getSupply().getSupID() == supply.getSupID()){
                retVal += modAndGetAmount(supply, saf.getAdditiveUnits(), saf.getAdditiveLbs(), count);
            }
        }
        return retVal;
    }
    
    private double modAndGetAmount(final Supplies supply, final String fromUnits
            , final float fromAmount, final int qty){
        final int toUnit = Units.getUnitFromShortDescription(supply.getSupUnits());
        final int fromUnit = Units.getUnitFromShortDescription(fromUnits);
        //Convert total amount used to supply units.
        final float supplyQty = Units.convertUnits(fromUnit, toUnit, (fromAmount*qty));
        //Calculate attirition if any exists.
        final float supplyAttrition = supply.getAttrition()/(float)100;
        final double totalAttrition = (double)supplyQty * (double)supplyAttrition;
        //return the total converted qty + total attrition.
        return (supplyQty + totalAttrition);
    }
    
    private Supplies getSupplyFromSAFID(final int id, final EntityManager en){
        TypedQuery<Supplies> query = en.createQuery(
                    "SELECT s FROM Supplies s WHERE s.supID = ?1", Supplies.class);
        query.setParameter(1, id);
        final Supplies supply = query.getSingleResult();
        return supply;
    }
    
    /***************************************************************************
     * Do a Supply receive
     * @param en - Entity Manager
     * @param supplyID - Supply ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doReceiveSupply(final EntityManager en, final int supplyID, final int qty){
        final Supplies supply = getSupplyFromSAFID(supplyID, en);
        final float newqty = qty * supply.getSupQtyPer();
        float newod = supply.getSupOnOrder() - newqty;
        if(newod < 0){
            newod = 0;
        }
        final float newoh = supply.getSupOnHand() + newqty;
        supply.setSupOnHand(newoh);
        supply.setSupOnOrder(newod);
        en.merge(supply);
    }
    
    /***************************************************************************
     * Do a Supply allocation addition.
     * @param en - Entity Manager
     * @param supplyID - Supply ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doSupplyAllocationAddition(final EntityManager en, final int supplyID, final int qty){
        final Supplies supply = getSupplyFromSAFID(supplyID, en);
        float newAllocation = supply.getSupSpoken() + (float)qty;
        if(newAllocation < 0){
            newAllocation = 0;
        }
        supply.setSupSpoken(newAllocation);
        en.merge(supply);
    }
    
    /***************************************************************************
     * Do a Supply allocation subtraction.
     * @param en - Entity Manager
     * @param supplyID - Supply ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doSupplyAllocationSubtraction(final EntityManager en, final int supplyID, final int qty){
        final Supplies supply = getSupplyFromSAFID(supplyID, en);
        float newAllocation = supply.getSupSpoken() - (float)qty;
        if(newAllocation < 0){
            newAllocation = 0;
        }
        supply.setSupSpoken(newAllocation);
        en.merge(supply);
    }
    
    /***************************************************************************
     * Do a Supply on order update
     * @param en - Entity Manager
     * @param supplyID - Supply ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doUpdateSupplyOnOrder(final EntityManager en, final int supplyID, final int qty){
        final Supplies supply = getSupplyFromSAFID(supplyID, en);
        float newQty = supply.getSupOnOrder() + (float)(qty * supply.getSupQtyPer());
        if(newQty < 0){
            newQty = 0;
        }
        supply.setSupOnOrder(newQty);
        en.merge(supply);
    }
    
    /***************************************************************************
     * FGI Modifiers
     **************************************************************************/
    /***************************************************************************
     * Do a FGI removal without adjusting allocation
     * @param en - Entity Manager
     * @param fgiID - FGI ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doFGIRemovalWithoutAllocation(final EntityManager en
            , final int fgiID, final long qty){
        TypedQuery<FinishedGoodsInventory> query = en.createQuery(
                "SELECT f FROM FinishedGoodsInventory f WHERE f.fgiid = ?1"
                , FinishedGoodsInventory.class);
        query.setParameter(1, fgiID);
        FinishedGoodsInventory fgi = query.getSingleResult();
        long qtyLeft = fgi.getFGIQtyOnHand() - qty;
        if(qtyLeft < 0){
            qtyLeft = 0;
        }
        fgi.setFGIQtyOnHand(qtyLeft);
        en.merge(fgi);
    }
    /***************************************************************************
     * Do a FGI serving tank removal without adjusting allocation
     * @param en - Entity Manager
     * @param fgiID - FGI ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doFGIServingTankRemovalWithoutAllocation(final EntityManager en
            , final int fgiID, final float qty){
        TypedQuery<FinishedGoodsInventory> query = en.createQuery(
                "SELECT f FROM FinishedGoodsInventory f WHERE f.fgiid = ?1"
                , FinishedGoodsInventory.class);
        query.setParameter(1, fgiID);
        FinishedGoodsInventory fgi = query.getSingleResult();
        float qtyLeft = fgi.getServingTankVolume() - qty;
        if(qtyLeft < 0){
            qtyLeft = 0;
        }
        fgi.setServingTankVolume(qtyLeft);
        en.merge(fgi);
    }
    /***************************************************************************
     * Do a FGI removal and adjust allocation
     * @param en - Entity Manager
     * @param fgiID - FGI ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doFGIRemovalWithAllocation(final EntityManager en
            , final int fgiID, final int qty){
        TypedQuery<FinishedGoodsInventory> query = en.createQuery(
                "SELECT f FROM FinishedGoodsInventory f WHERE f.fgiid = ?1"
                , FinishedGoodsInventory.class);
        query.setParameter(1, fgiID);
        FinishedGoodsInventory fgi = query.getSingleResult();
        long qtyLeft = fgi.getFGIQtyOnHand() - (long)qty;
        int allocated = fgi.getFGIQtyAllocated() - qty;
        if(qtyLeft < 0) qtyLeft = 0;
        if(allocated < 0) allocated = 0;
        fgi.setFGIQtyOnHand(qtyLeft);
        fgi.setFGIQtyAllocated(allocated);
        en.merge(fgi);
    }
    /***************************************************************************
     * Do a FGI allocation addition.
     * @param en - Entity Manager
     * @param fgiID - FGI ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doFGIAllocationAddition(final EntityManager en
            , final int fgiID, final int qty){
        TypedQuery<FinishedGoodsInventory> query = en.createQuery(
                "SELECT f FROM FinishedGoodsInventory f WHERE f.fgiid = ?1"
                , FinishedGoodsInventory.class);
        query.setParameter(1, fgiID);
        FinishedGoodsInventory fgi = query.getSingleResult();
        int allocation = fgi.getFGIQtyAllocated() + qty;
        if(allocation < 0){
            allocation = 0;
        }
        fgi.setFGIQtyAllocated(allocation);
        en.merge(fgi);
    }
    
    /***************************************************************************
     * Do a FGI allocation subtraction.
     * @param en - Entity Manager
     * @param fgiID - FGI ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doFGIAllocationSubtraction(final EntityManager en
            , final int fgiID, final int qty){
        TypedQuery<FinishedGoodsInventory> query = en.createQuery(
                "SELECT f FROM FinishedGoodsInventory f WHERE f.fgiid = ?1"
                , FinishedGoodsInventory.class);
        query.setParameter(1, fgiID);
        FinishedGoodsInventory fgi = query.getSingleResult();
        int allocation = fgi.getFGIQtyAllocated() - qty;
        if(allocation < 0){
            allocation = 0;
        }
        fgi.setFGIQtyAllocated(allocation);
        en.merge(fgi);
    }
    
    /***************************************************************************
     * Update all FGIs based on system pending sales only.
     * @param en - Entity Manager
     **************************************************************************/
    public void runFGIAllocationFix(final EntityManager en){
        TypedQuery<PendingSO> psoQuery = en.createQuery(
            "SELECT p FROM PendingSO p WHERE p.pSOComplete = 'false'"
            , PendingSO.class);
        List<PendingSO> psos = psoQuery.getResultList();
        List<FinishedGoodsInventory> fgis = en.createNamedQuery("FinishedGoodsInventory.findAll"
                , FinishedGoodsInventory.class).getResultList();
        if(psos != null){
            for(final FinishedGoodsInventory fgi : fgis){
                int qtyAllocated = 0;
                for(final PendingSO pso : psos){
                    if(pso.getPSOLinesCollection()!=null){
                        for(final PSOLines psol : pso.getPSOLinesCollection()){
                            if(psol.getPSOFGI()!=null){
                                if(psol.getPSOFGI().getFgiid() == fgi.getFgiid()){
                                    qtyAllocated += psol.getPSOQty();
                                }
                            }
                        }
                    }
                }
                if(qtyAllocated != fgi.getFGIQtyAllocated()){
                    fgi.setFGIQtyAllocated(qtyAllocated);
                    en.merge(fgi);
                }
            }
        }
    }
    
    /***************************************************************************
     * Do a FGI return without allocation.
     * @param en - Entity Manager
     * @param fgiID - FGI ID to be modified.
     * @param qty - Quantity to be added.
     **************************************************************************/
    public void doFGIReturnWithoutAllocation(final EntityManager en
            ,final int fgiID, final int qty){
        TypedQuery<FinishedGoodsInventory> query = en.createQuery(
                "SELECT f FROM FinishedGoodsInventory f WHERE f.fgiid = ?1"
                , FinishedGoodsInventory.class);
        query.setParameter(1, fgiID);
        FinishedGoodsInventory fgi = query.getSingleResult();
        long newQty = fgi.getFGIQtyOnHand() + (long)qty;
        if(newQty < 0){
            newQty = 0;
        }
        fgi.setFGIQtyOnHand(newQty);
        en.merge(fgi);
    }
    
    /***************************************************************************
     * Do a FGI serving vessel return without allocation.
     * @param en - Entity Manager
     * @param fgiID - FGI ID to be modified.
     * @param volume - Quantity to be added.
     **************************************************************************/
    public void doFGIServingVesselReturnWithoutAllocation(final EntityManager en
            ,final int fgiID, final float volume){
        TypedQuery<FinishedGoodsInventory> query = en.createQuery(
                "SELECT f FROM FinishedGoodsInventory f WHERE f.fgiid = ?1"
                , FinishedGoodsInventory.class);
        query.setParameter(1, fgiID);
        FinishedGoodsInventory fgi = query.getSingleResult();
        float newQty = fgi.getServingTankVolume() + volume;
        if(newQty < 0){
            newQty = 0;
        }
        fgi.setServingTankVolume(newQty);
        en.merge(fgi);
    }
    
    /***************************************************************************
     * Do a FGI addition.
     * @param en - Entity Manager
     * @param fgi - FGI ID to be modified.
     * @param qty - Quantity to be added.
     **************************************************************************/
    public void doFGIAddition(final EntityManager en
            ,final FinishedGoodsInventory fgi, final int qty){
        //load the supplies from database.
        if(fgi.getInvUsageCollection()!=null){
            for(final InventoryUsage iu : fgi.getInvUsageCollection()){
                Supplies supply = getSupplyFromSAFID(iu.getSupplyID().getSupID(), en);
                final float qtyToRemove = (float)modAndGetAmount(supply, iu.getUnits(), iu.getAmount(), qty);
                doSupplyRemovalWithAllocation(en, supply.getSupID(), qtyToRemove);
            }
        }
        if(fgi.getFGIPackageID().getInvUsageCollection()!=null){
            for(final InventoryUsage iu : fgi.getFGIPackageID().getInvUsageCollection()){
                Supplies supply = getSupplyFromSAFID(iu.getSupplyID().getSupID(), en);
                final float qtyToRemove = (float)modAndGetAmount(supply, iu.getUnits(), iu.getAmount(), qty);
                doSupplyRemovalWithAllocation(en, supply.getSupID(), qtyToRemove);
            }
        }

        final long newQty = fgi.getFGIQtyOnHand() + (long)qty;
        fgi.setFGIQtyOnHand(newQty);
        en.merge(fgi);
    }
    
    /***************************************************************************
     * Do a FGI addition.
     * @param en - Entity Manager
     * @param fgi - FGI ID to be modified.
     * @param qty - Quantity to be added.
     **************************************************************************/
    public void doFGIServingVesselAddition(final EntityManager en
            ,final FinishedGoodsInventory fgi, final float qty){
        //load the supplies from database.
        final float newQty = fgi.getServingTankVolume() + qty;
        fgi.setServingTankVolume(newQty);
        en.merge(fgi);
    }
    
    /***************************************************************************
     * Hops Modifiers
     **************************************************************************/
    /***************************************************************************
     * Update all Hops based on system pending usage only.
     * @param en - Entity Manager
     **************************************************************************/
    public void runHopAllocationFix(final EntityManager en){
        final Date stopDate = getPODate(en);
        List<Hops> hops = en.createNamedQuery("Hops.findAll").getResultList();
        List<Brews> brewItems = en.createNamedQuery("Brews.findInProcess").getResultList();
        List<PurchaseOrder> pos = en.createNamedQuery("PurchaseOrder.findOnOrderPOs").getResultList();
        for(final Hops hop : hops){
            float hopAll = 0;
            for(final Brews brew : brewItems){
                if(brew.getGanttStart().before(stopDate) || brew.getGanttStart().equals(stopDate)){
                    if(brew.getBState() < MovementState.FERMENTING.getState()){
                        hopAll += getHopAmt(brew, hop);
                    } else if(brew.getBState() < MovementState.CONDITIONING.getState()){
                        hopAll += getDryHopAmt(brew, hop);
                    }
                }
            }
            if(hopAll != hop.getHopSpoken()){
                hop.setHopSpoken(hopAll);
                en.merge(hop);
            }
        }
        for(final Hops hop : hops){
            //Now reconcile what is on order and what isn't.
            int hopOnOrder = 0;
            for(final PurchaseOrder po : pos){
                if(po.getPOLinesCollection()!=null){
                    for(final POLines pol : po.getPOLinesCollection()){
                        if(pol.getPolpn().getWhid().getWearhouseID() == WarehouseIngredient.HOPS.getDbId()){
                            if(pol.getPolpn().getTableID() == hop.getHid()){
                                if(!pol.getPOLRcvd()){
                                    int qty = (pol.getPOLQty() - pol.getpOLQtyRcvd());
                                    if(qty<0){
                                        qty=0;
                                    }
                                    hopOnOrder += qty;
                                }
                            }
                        }
                    }
                }
            }
            hop.setHopOnOrder((float)hopOnOrder);
        }
    }
     
    private float getDryHopAmt(final Brews brew, final Hops hop){
        float retVal = 0;
        List<DryHops> dryHops = brew.getRid().getDryHopCollection();
        if(dryHops != null){
            for(final DryHops dryHop : dryHops){
                if(dryHop.getHopID().getHid() == hop.getHid()){
                    retVal += (dryHop.getDryHopLbs());
                }
            }
        }
        return retVal;
    }
     
    private float getHopAmt(final Brews brew, final Hops hop){
        //Get count of brews to add per brew note.
        int count = getBrewHopCount(brew);
        float retVal = 0;
        for(final BoilHops bht : brew.getRid().getBoilHopCollection()){
            if(bht.getHopID().getHid() == hop.getHid()){
                retVal += (bht.getBoilHopLbs() * count);
            }
        }
        //Now get dry hopping amount without count since we don't hop more than once per brew.
        retVal += getDryHopAmt(brew, hop);
        return retVal;
    }
      
    private int getBrewHopCount(final Brews brew){
        int count = 0;
        if(brew.getBrewNoteID()!=null && addHopCount(brew.getBrewNoteID())){
            count++;
        }
        if(brew.getBrewNoteID2()!=null && addHopCount(brew.getBrewNoteID2())){
            count++;
        }
        if(brew.getBrewNoteID3()!=null && addHopCount(brew.getBrewNoteID3())){
            count++;
        }
        if(brew.getBrewNoteID4()!=null && addHopCount(brew.getBrewNoteID4())){
            count++;
        }
        if(brew.getBrewNoteID5()!=null && addHopCount(brew.getBrewNoteID5())){
            count++;
        }
        if(brew.getBrewNoteID6()!=null && addHopCount(brew.getBrewNoteID6())){
            count++;
        }
        return count;
    }
    
    private boolean addHopCount(final BrewNotes bn){
        boolean retVal = true;
        for(final BoilHopNotes bhn : bn.getBoilHopNoteCollection()){
            if(bhn.getBoilHopLbs()>0){
                retVal = false;
            }
        }
        return retVal;
    }
    
    /***************************************************************************
     * Do a supply removal without adjusting allocation
     * @param en - Entity Manager
     * @param hopID - Hop ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doHopRemovalWithAllocation(final EntityManager en
            , final int hopID, final float qty){
        TypedQuery<Hops> query = en.createQuery(
                "SELECT h FROM Hops h WHERE h.hid = ?1", Hops.class);
        query.setParameter(1, hopID);
        Hops hop = query.getSingleResult();
        float qtyLeft = hop.getHopOnHand() - qty;
        float allocated = hop.getHopSpoken() - qty;
        if(qtyLeft < 0){
            qtyLeft = 0;
        }
        if(allocated < 0){
            allocated = 0;
        }
        hop.setHopOnHand(qtyLeft);
        hop.setHopSpoken(allocated);
        en.merge(hop);
    }
    
    /***************************************************************************
     * Do a supply return without adjusting allocation
     * @param en - Entity Manager
     * @param hopID - Hop ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doHopReturnWithoutAllocation(final EntityManager en
            , final int hopID, final float qty){
        TypedQuery<Hops> query = en.createQuery(
                "SELECT h FROM Hops h WHERE h.hid = ?1", Hops.class);
        query.setParameter(1, hopID);
        Hops hop = query.getSingleResult();
        hop.setHopOnHand(hop.getHopOnHand() + qty);
        en.merge(hop);
    }
    
    /***************************************************************************
     * Do a Hop Receive
     * @param en - Entity Manager
     * @param hopID - Hop ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doReceiveHops(final EntityManager en, final int hopID, final int qty){
        TypedQuery<Hops> query = en.createQuery(
            "SELECT h FROM Hops h WHERE h.hid = ?1", Hops.class);
        query.setParameter(1, hopID);
        Hops hop = query.getSingleResult();
        final float newqty = qty * hop.getHopBagLbs();
        float newod = hop.getHopOnOrder() - newqty;
        if(newod < 0){
            newod = 0;
        }
        final float newoh = hop.getHopOnHand() + newqty;
        hop.setHopOnOrder(newod);
        hop.setHopOnHand(newoh);
        en.merge(hop);
    }
    
    /***************************************************************************
     * Do a Hop on order update
     * @param en - Entity Manager
     * @param hopID - Hop ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doUpdateHopsOnOrder(final EntityManager en, final int hopID, final int qty){
        TypedQuery<Hops> query = en.createQuery(
            "SELECT h FROM Hops h WHERE h.hid = ?1", Hops.class);
        query.setParameter(1, hopID);
        Hops hop = query.getSingleResult();
        float newQty = hop.getHopOnOrder() + (float)(qty * hop.getHopBagLbs());
        if(newQty < 0){
            newQty = 0;
        }
        hop.setHopOnOrder(newQty);
        en.merge(hop);
    }
    
    /***************************************************************************
     * YeastGen Modifiers
     **************************************************************************/
    
    /***************************************************************************
     * Do a supply removal without adjusting allocation
     * @param en - Entity Manager
     * @param yeastGenID
     * @param volume
     **************************************************************************/
    public void doYeastGenRemoval(final EntityManager en
            , final int yeastGenID, final float volume){
        TypedQuery<YeastGens> query = en.createQuery(
                "SELECT y FROM YeastGens y WHERE y.yeastGenID = ?1", YeastGens.class);
        query.setParameter(1, yeastGenID);
        YeastGens yg = query.getSingleResult();
        float volumeLeft = yg.getyGVolume() - volume;
        if(volumeLeft < 0){
            volumeLeft = 0;
        }
        yg.setyGVolume(volumeLeft);
        if(volumeLeft <= 0){
            yg.setYGPitchedFilter(true);
        }
        en.merge(yg);
    }
    
    /***************************************************************************
     * Do a supply return without adjusting allocation
     * @param en - Entity Manager
     * @param yeastGenID
     * @param volume
     **************************************************************************/
    public void doYeastGenReturn(final EntityManager en
            , final int yeastGenID, final float volume){
        TypedQuery<YeastGens> query = en.createQuery(
                "SELECT y FROM YeastGens y WHERE y.yeastGenID = ?1", YeastGens.class);
        query.setParameter(1, yeastGenID);
        YeastGens yg = query.getSingleResult();
        yg.setyGVolume(yg.getyGVolume() + volume);
        if(yg.getyGVolume()>0){
            yg.setYGPitchedFilter(false);
        }
        en.merge(yg);
    }
    
    /***************************************************************************
     * Malt Modifiers
     **************************************************************************/
    /***************************************************************************
     * Do a supply removal without adjusting allocation
     * @param en - Entity Manager
     * @param maltID - Malt ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doMaltRemovalWithAllocation(final EntityManager en
            , final int maltID, final float qty){
        TypedQuery<Malt> query = en.createQuery(
                "SELECT m FROM Malt m WHERE m.maltID = ?1", Malt.class);
        query.setParameter(1, maltID);
        Malt malt = query.getSingleResult();
        float qtyLeft = malt.getMaltOnHand() - qty;
        float allocated = malt.getMaltSpoken() - qty;
        if(qtyLeft < 0){
            qtyLeft = 0;
        }
        if(allocated < 0){
            allocated = 0;
        }
        malt.setMaltOnHand(qtyLeft);
        malt.setMaltSpoken(allocated);
        en.merge(malt);
    }
    
    /***************************************************************************
     * Do a Malt return without adjusting allocation
     * @param en - Entity Manager
     * @param maltID - Malt ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doMaltReturnWithoutAllocation(final EntityManager en
            , final int maltID, final float qty){
        TypedQuery<Malt> query = en.createQuery(
                "SELECT m FROM Malt m WHERE m.maltID = ?1", Malt.class);
        query.setParameter(1, maltID);
        Malt malt = query.getSingleResult();
        malt.setMaltOnHand(malt.getMaltOnHand() + qty);
        en.merge(malt);
    }
    
    /***************************************************************************
     * Do a Malt return without adjusting allocation
     * @param en - Entity Manager
     * @param maltID - Malt ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doMaltReturnWithAllocation(final EntityManager en
            , final int maltID, final float qty){
        TypedQuery<Malt> query = en.createQuery(
                "SELECT m FROM Malt m WHERE m.maltID = ?1", Malt.class);
        query.setParameter(1, maltID);
        Malt malt = query.getSingleResult();
        malt.setMaltOnHand(malt.getMaltOnHand() + qty);
        malt.setMaltSpoken(malt.getMaltSpoken() + qty);
        en.merge(malt);
    }
    
    /***************************************************************************
     * Update all Malt based on system pending usage only.
     * @param en - Entity Manager
     **************************************************************************/
    public void runMaltAllocationFix(final EntityManager en){
        final Date stopDate = getPODate(en);
        List<Malt> maltItems = en.createNamedQuery("Malt.findAll").getResultList();
        List<Brews> brewItems = en.createNamedQuery("Brews.findInProcess").getResultList();
        List<PurchaseOrder> pos = en.createNamedQuery("PurchaseOrder.findOnOrderPOs").getResultList();
        for(final Malt malt : maltItems){
            float maltAll = 0;
            for(final Brews brew : brewItems){
                if(brew.getGanttStart().before(stopDate) || brew.getGanttStart().equals(stopDate)){
                    if(brew.getBState() < MovementState.FERMENTING.getState()){
                        maltAll += getMaltAmt(brew, malt);
                    } 
                }
            }
            if(maltAll != malt.getMaltSpoken()){
                malt.setMaltSpoken(maltAll);
                en.merge(malt);
            }
        }
        for(final Malt malt : maltItems){
            //Now reconcile what is on order and what isn't.
            int maltOnOrder = 0;
            for(final PurchaseOrder po : pos){
                if(po.getPOLinesCollection()!=null){
                    for(final POLines pol : po.getPOLinesCollection()){
                        if(pol.getPolpn().getWhid().getWearhouseID() == WarehouseIngredient.MALT_AND_ADJUNCTS.getDbId()){
                            if(pol.getPolpn().getTableID() == malt.getMaltID()){
                                if(!pol.getPOLRcvd()){
                                    int qty = (pol.getPOLQty() - pol.getpOLQtyRcvd());
                                    if(qty<0){
                                        qty=0;
                                    }
                                    maltOnOrder += qty;
                                }
                            }
                        }
                    }
                }
            }
            malt.setMaltOnOrder((float)maltOnOrder);
        }
    }
    
    private Date getPODate(final EntityManager en){
        final Date today = new Date();
        final Administration admin = getAdmin(en);
        Calendar cal = Calendar.getInstance();
        cal.setTime(today);
        cal.add(Calendar.DAY_OF_YEAR, admin.getDaysForPO());
        return cal.getTime();
    }
    
    private Administration getAdmin(final EntityManager en){
        TypedQuery<Administration> query = en.createQuery(
            "SELECT a FROM Administration a WHERE a.aNum = '1'", Administration.class);
        Administration admin = query.getSingleResult();
        return admin;
    }
        
    private float getMaltAmt(final Brews brew, final Malt malt){
        int count = getBrewMaltCount(brew);
        float retVal = 0;
        //List<MaltPair> mps = createMaltPair(brew.getRid());
        for(final MaltBill mb : brew.getRid().getMaltBillCollection()){
            if(mb.getMaltID().getMaltID() == malt.getMaltID()){
                retVal += (mb.getMaltLbs() * count);
            }
        }
        return retVal;
    }
        
    private int getBrewMaltCount(final Brews brew){
        int count = 0;

        if(brew.getBrewNoteID()!=null && addMaltCount(brew.getBrewNoteID())){
            count++;
        }
        if(brew.getBrewNoteID2()!=null && addMaltCount(brew.getBrewNoteID2())){
            count++;
        }
        if(brew.getBrewNoteID3()!=null && addMaltCount(brew.getBrewNoteID3())){
            count++;
        }
        if(brew.getBrewNoteID4()!=null && addMaltCount(brew.getBrewNoteID4())){
            count++;
        }
        if(brew.getBrewNoteID5()!=null && addMaltCount(brew.getBrewNoteID5())){
            count++;
        }
        if(brew.getBrewNoteID6()!=null && addMaltCount(brew.getBrewNoteID6())){
            count++;
        }

        return count;
    }
        
    private boolean addMaltCount(final BrewNotes bn){
        boolean retVal = true;
        for(final MaltNotes mn : bn.getMaltNoteCollection()){
            if(mn.getMaltNoteLbs()>0){
                retVal = false;
            }
        }
        return retVal;
    }
    
    /***************************************************************************
     * Do a Malt receive
     * @param en - Entity Manager
     * @param maltID - Hop ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doReceiveMalt(final EntityManager en, final int maltID, final int qty){
        TypedQuery<Malt> query = en.createQuery(
                "SELECT m FROM Malt m WHERE m.maltID = ?1", Malt.class);
        query.setParameter(1, maltID);
        Malt malt = query.getSingleResult();
        final float newqty = qty * (float) malt.getMaltBagLbs();
        float newod = malt.getMaltOnOrder() - newqty;
        if(newod < 0){
            newod = 0;
        }
        final float newoh = malt.getMaltOnHand() + newqty;
        malt.setMaltOnOrder(newod);
        malt.setMaltOnHand(newoh);
        en.merge(malt);
    }
    
    /***************************************************************************
     * Do a Malt on order update
     * @param en - Entity Manager
     * @param maltID - Hop ID to be modified.
     * @param qty - Quantity to be removed.
     **************************************************************************/
    public void doUpdateMaltOnOrder(final EntityManager en, final int maltID, final int qty){
        TypedQuery<Malt> query = en.createQuery(
                "SELECT m FROM Malt m WHERE m.maltID = ?1", Malt.class);
        query.setParameter(1, maltID);
        Malt malt = query.getSingleResult();
        float newQty = malt.getMaltOnOrder() + (float)(qty * malt.getMaltBagLbs());
        if(newQty < 0){
            newQty = 0;
        }
        malt.setMaltOnOrder(newQty);
        en.merge(malt);
    }
}
